R-Version: [Default] [32-bit] C:\Program Files\R\R-4.1.0


Installieren der Packete

packages <- c("tidyverse", "data.table", "lubridate", "ggplot2", "ggthemes", "recommenderlab")

# Noch nicht installierte Pakete installieren
installed_packages <- packages %in% rownames(installed.packages())

if (any(installed_packages == FALSE)) {
  install.packages(packages[!installed_packages])
}

# Laden der Packete
invisible(lapply(packages, library, character.only = TRUE))

# summaries zu "TRUE" setzen um summaries anzuzeigen
summaries = TRUE

Datenimport

data(MovieLense)
MovieLense
943 x 1664 rating matrix of class ‘realRatingMatrix’ with 99392 ratings.
## look at the first few ratings of the first user
head(as(MovieLense[1,], "list")[[1]])
                                    Toy Story (1995)                                     GoldenEye (1995) 
                                                   5                                                    3 
                                   Four Rooms (1995)                                    Get Shorty (1995) 
                                                   4                                                    3 
                                      Copycat (1995) Shanghai Triad (Yao a yao yao dao waipo qiao) (1995) 
                                                   3                                                    5 
## visualize part of the matrix
image(MovieLense[1:100,1:100])


## number of ratings per user
hist(rowCounts(MovieLense))


## number of ratings per movie
hist(colCounts(MovieLense))


## mean rating (averaged over users)
mean(rowMeans(MovieLense))
[1] 3.587565
## available movie meta information
head(MovieLenseMeta)

## available user meta information
head(MovieLenseUser["id"])
NA

alle charakter variabeln faktorisieren


movies <- as(MovieLense, "data.frame")

movies <- movies %>% mutate_if(is.character, as.factor)

head(movies)
NA
movies_wider <- pivot_wider(
  movies,
  id_cols = user,
  names_from = item,
  values_from = rating,
  values_fill = NULL,
)

head(movies_wider)

Explorative Datenanalyse

df_1 <- movies %>% group_by(item) %>%  summarize(mean_rating = mean(rating)) %>% sample_n(15) %>% arrange(desc(mean_rating))
`summarise()` ungrouping output (override with `.groups` argument)
ggplot(df_1, aes(y = reorder(item, +mean_rating), x = mean_rating)) +
  geom_col(alpha = 1, fill = 'steelblue') +
  scale_y_discrete(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  geom_text(aes(label=round(mean_rating,2)), hjust = 1.3, color = 'white') +
  labs(
    title = "Average movie ratings",
    subtitle = "Random Sample of 15 Movies",
    y = element_blank(),    x = "Average Rating (stars)"
  ) +
  theme_classic() +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.line.x = element_blank(),
        text = element_text(size = 12) # text size
  )

NA
NA

1. Welches sind die am häufigsten geschauten Genres / Filme?

movies_genre <- MovieLenseMeta%>%
  rename(item = title)
movies_genre$url <- NULL
movies_genre[movies_genre == 0] <- NA
a <- which(movies_genre==1,arr.ind=TRUE)
movies_genre[a] <- names(movies_genre)[a[,"col"]]
movies_genre <- movies_genre %>%
  unite("genres", unknown:Western, sep= ",", 
        remove = TRUE, na.rm = TRUE)
movies_genre

movies_mean <- movies%>%
  group_by(item)%>%
  summarise(mean_rating = mean(rating))
`summarise()` ungrouping output (override with `.groups` argument)
df2_movies<-merge(x=movies,y=movies_genre,by="item",all.x=TRUE)%>%
  mutate(genres = strsplit(as.character(genres), ",")) %>%
  unnest(genres)

df2_mean<-merge(x=movies_mean,y=movies_genre,by="item",all.x=TRUE)%>%
  mutate(genres = strsplit(as.character(genres), ",")) %>%
  unnest(genres)
df2_movies
df1_movies <- df2%>%
  group_by(item)%>%
  summarize(count=n())%>%
  ungroup()%>%
  arrange(desc(count))
`summarise()` ungrouping output (override with `.groups` argument)
df1_movies <- head(df1_movies, 10)
df1_movies

df1_movies%>%
  mutate(item = fct_reorder(item, count))%>%
  ggplot(aes(x = count, y = item))+
  geom_col(alpha = 1, fill = 'steelblue')+
  scale_y_discrete(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  geom_text(aes(label=count,2), hjust = 1.3, color = 'white') +
  labs(
    title = "Most rated Movies",
    y = element_blank(),    x = "Count of ratings"
  ) +
  theme_classic() +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.line.x = element_blank(),
        text = element_text(size = 12) # text size
  )

Da in unserem datensatz nur die Anzahl Rarings von Filmen gegeben ist, gehen wir davon aus, dass die meist bewerteten Filme auch die am häufigsten geschauten filme sind. in der Grafik sieht man die 10 meist bewerteten Filme, wobei die ersten drei Plätze von Star Wars Filmen besetzt sind.

df1_genres <- df2%>%
  group_by(genres)%>%
  summarize(count=n())%>%
  ungroup()%>%
  arrange(desc(count))
`summarise()` ungrouping output (override with `.groups` argument)
df1_genres%>%
  mutate(genres = fct_reorder(genres, count))%>%
  ggplot(aes(x = count, y = genres))+
  geom_col(alpha = 1, fill = 'steelblue')+
  scale_y_discrete(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  geom_text(aes(label=count,2), hjust = 1.3, color = 'white') +
  labs(
    title = "Most rated Genres",
    y = element_blank(),    x = "Count of ratings"
  ) +
  theme_classic() +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.line.x = element_blank(),
        text = element_text(size = 12) # text size
  )

Auch hier wirs savon ausgegangen, dass die am meisten bewerteten Genres such die am häufigst geschauten Genres sind. In der Grafik ist zu sehen, dass Drama das top Genres ist, gefolgt von Comedy und Action.

2. Wie verteilen sich die Kundenratings gesamthaft und nach Genres?

ggplot(movies, aes(x = rating)) +
  geom_bar(alpha = 1, fill = 'steelblue') +
  scale_y_continuous(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  labs(
    title = "Verteilung Kundenratings gesamthaft",
    subtitle = paste("N = ", nrow(df2), " Bewertungen"),
    x = "Kundenbewertungen", 
    y = "Dichte",
    fill = element_blank()
  ) +
  theme_classic() +
  theme(
    text = element_text(size = 12),
    legend.position = 'bottom'
  )

In dieser Grafik ist die Verteilung der bewertungen zu sehen. Die Bewertungen 4 und 5 wirden klar am häufigsten vergeben, wobei 1 und 2 eher selten bewertet werden.

ggplot(df2, aes(x = rating, fill = genres)) +
  geom_bar(alpha = 1, bins = 10) +
  facet_wrap(~genres)+
  scale_y_continuous(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  labs(
    title = "Verteilung Kundenratings nach Genres",
    subtitle = paste("N = ", nrow(df2), " Bewertungen"),
    x = "Durchschnittliche Bewertung", 
    y = "Dichte",
    fill = element_blank()
  ) +
  theme(
    text = element_text(size = 12),
    legend.position = 'none'
  )
Ignoring unknown parameters: bins

Hier ist zu sehen, dass das Genres Drama am meisten bewertet wurde, wobei Dokumentationen am wenigsten Bewertungen erhalten haben. Die Bewertungen pro Genres verteilen sich jeweils sehr ähnlich. Die Verteilungen der einzelnen Genres sind ebenfalls ähnlich verteilt wie die bewertungen gesamthaft.


3.Wie verteilen sich die mittleren Kundenratings pro Film?

ggplot(df3, aes(x = mean_rating)) +
  geom_density(alpha = 1, fill = 'steelblue') +
  scale_y_continuous(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  labs(
    title = "Verteilung mittlere Kundenratings pro Film",
    subtitle = "N = 1664 Filme",
    x = "Durchschnittliche Bewertung", 
    y = "Dichte",
  ) +
  theme_classic() +
  theme(text = element_text(size = 12)
  )
Fehler: Argument 3 is empty
Run `rlang::last_error()` to see where the error occurred.

In dieser Grafik ist die durchschnittliche Bewertung pro Film zu sehen, wobei auch hier zu sehen ist ,dass die die meisten Filme eine Durchschnittliche Bewertung von ca. 4 haben

ggplot(df3, aes(x = mean_rating, fill = more_than_50)) +
  geom_density(alpha = 0.5) +
  scale_y_continuous(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  labs(
    title = "Verteilung mittlere Kundenratings pro Film",
    subtitle = "N = 1664 Filme",
    x = "Durchschnittliche Bewertung", 
    y = "Dichte",
    fill = element_blank()
  ) +
  theme_classic() +
  theme(
    text = element_text(size = 12),
    legend.position = 'bottom'
  )

Für diese Grafik wurden die Filme in zwei gruppen unterteilt: Filme die weniger als 50 bewertungen erhalten haben, und Filme welche mehr als 50 Bewertungen erhalten haben. In der Grafik ist imernoch die durchschnittliche Bewertung dieser Filme zu sehen wobei deutlich erkannt werden kann, dass filme welche weniger bewertungen erhalten haben, tendenziell auch schlechter bewertet wurden.

4.Wie stark streuen die Ratings von individuellen Kunden?

# get rating count per user, add as column for further processing
counts <- movies %>% group_by(user) %>% count()
movies <- merge(movies, counts, by="user")
movies_wider <- merge(movies_wider, counts, by="user")

# avoid users with almost no ratings, use median as threshold
median_count <- median(counts$n)
print(median_count)
[1] 64
# get sample
set.seed(623)
movies_sample <- movies_wider %>% filter(n > median_count) %>% sample_n(5)

# create long table
movies_sample_long <- filter(movies, user %in% movies_sample$user)

# drop item names, 
movies_sample_long <- subset(movies_sample_long, select = -c(item))

movies_sample
movies_sample_long
NA
NA
# Number of ratings per user per rating value
movies_sample_long_grouped <- movies_sample_long %>% group_by(user, rating) %>% summarise(rating_dens = length(user) / first(n), user = first(user), n=first(n), rating = first(rating))
`summarise()` regrouping output by 'user' (override with `.groups` argument)
movies_sample_long_grouped
movies_sample_long
NA
rlang::last_error()
<error/rlang_error>
Argument 3 is empty
Backtrace:
 1. ggplot2::labs(...)
 2. rlang::list2(...)
Run `rlang::last_trace()` to see the full context.

In dieser Grafik sehen wir, wie sich die Bewertungen einzelner Kunden verteilen. Auffallend ist generell, dass die Bewertungen 1 und 2 weniger oft abgegeben wurde als 3 und 4. Bei der Verteilung der ratings sind von User zu User Unterschiede feststellbar. User 24 bewertet beispielsweise viel besser als User 639. Dies könnte bedeuten, dass User 24 nur Filme bewertet oder schaut die er/sie mag, oder grundsätzlich höhere Bewertungen abgibt. Leider sehen wir hier weniger gut, welche Tendenzen die Streuung der Rating aller User aufweisen.


movies_span <- movies %>% group_by(user) %>% 
  summarize(mean = mean(rating), min = min(rating), max = max(rating), span = (max(rating) - min(rating)))
`summarise()` ungrouping output (override with `.groups` argument)
movies_span
NA
set.seed(123)

ggplot(sample_n(movies_span, 20), aes(x=user)) +
  geom_point(colour="black", aes(y=mean), shape=21) +
  geom_errorbar(aes(ymin=min, ymax=max)) +
  labs(
    title = "Spannweite Kundenratings ",
    subtitle = "N = 20 Kunden",
    x = "User ID", 
    y = "Rating Range",
  )
Fehler: Argument 3 is empty
Run `rlang::last_error()` to see where the error occurred.

In diesen Grafiken sehen wir detailliertere Informationen über die Spannweite und den Mittelpunkt. In der ersten Übersicht ist die Spannweite und der Mittelpunkt einzelner Kunden dargestellt. Es fällt auf, dass trotz des teilweise relativ hohem Mittelwert alle Ratings von 1-5 abgegeben wurden. Ein rating von 5 wurde sozusagen immer abgegeben, 1 nicht immer. In der zweiten Übersicht ist die Spannweite aller Kunden dargestellt. Hier wird sichtbar, dass die meisten Kunden Bewertungen von 1-5 abgegeben haben (Spannweite=4), und nur weinige sehr homogen bewertet haben (Spannweite = 1/2). Eine kleine Spannweite kann hier auch aufgetreten sein, da diese User sehr wenige Bewertungen abgegeben haben.

5.Welchen Einfluss hat die Normierung der Ratings pro Kunde auf deren Verteilung?
hist(getRatings(MovieLense), breaks=15)

hist(getRatings(MovieLenseNorm), breaks=40)

Die Ratings sind nun ungefähr Normalverteilt mit einem Durchschnittsrating von 0 und einer Standardabweichung von 1. Erkennbar ist, dass die Verteilung rechtssteil und linksschief ist, also mehrheitlich positive Bewertungen abgegeben wurden. Durch die Normierung der Daten werden die Ratings jedes Users auf dieselbe Verteilung gestaucht, wodurch man die Verteilung aller Daten analysieren kann. Dadurch hat man beispielsweise die Möglichkeit die durchschnittliche Bewertungstendenz herauszufinden.

6.Welche strukturellen Charakteristika (z.B. Sparsity) und Auffälligkeiten zeigt die User Item Matrix?
image(MovieLense, main = "Raw Ratings")


MovieLenseNorm <- normalize(MovieLense, method="Z-score")
image(MovieLenseNorm, main = "Normalized Ratings")


Users mit tiefen ID’s und Filme mit hohen ID’s weisen weniger ratings auf. Filme mit tiefer ID jedoch sehr viele. Auffallend ist, dass es einige wenige User gibt, die fast alle Filme bewertet haben (erkennbar durch die horizontalen scharzen Striche). Dies scheinen sehr aktive Bewerter zu sein. Viele Users haben jedoch nur einen kleinen Teil der Filme bewertet. Bei den Filmen ist eine ähnliche Tendenz wahrzunehmen, jedoch sind die vertikalen Striche breiter. Möglicherweise sind dort einige beliebte Filme zusammengefasst.


Datenreduktion
# convert into df
data <- as(MovieLense, "data.frame")

# get the 400 users with most ratings
counts <- data %>% group_by(user) %>% count() %>% arrange(desc(n), user) %>% head(400)
data <- inner_join(counts, data, by="user")
data <- data %>% select(user, item, rating) %>% ungroup
data <- as.data.frame(data)

# get the 700 Movies with most ratings
counts <- data %>% group_by(item) %>% count() %>% arrange(desc(n), item) %>% head(700)
data <- inner_join(counts, data, by="item")
data <- data %>% select(user, item, rating) %>% ungroup
data <- as.data.frame(data)

# convert back into realRatingMatrix
ratingMatrix <- as(data, "realRatingMatrix")
ratingMatrix
400 x 700 rating matrix of class ‘realRatingMatrix’ with 67765 ratings.
print(paste('Old Matrix (',toString(dim(MovieLense)), ')'))
[1] "Old Matrix ( 943, 1664 )"
print(paste('Non NA Values:', round((nratings(MovieLense) / (dim(MovieLense)[1] * dim(MovieLense)[2])) * 100,2), '%' ))
[1] "Non NA Values: 6.33 %"
print('')
[1] ""
print(paste('New Matrix (',toString(dim(ratingMatrix)), ')'))
[1] "New Matrix ( 400, 700 )"
print(paste('Non NA Values:', round((nratings(ratingMatrix) / (dim(ratingMatrix)[1] * dim(ratingMatrix)[2])) * 100,2), '%'))
[1] "Non NA Values: 24.2 %"
old_matrix <- as(MovieLense, "data.frame") %>% 
  group_by(item) %>%  
  summarize(
    mean_rating = mean(rating),
    ratings = n()
  ) %>% 
  mutate(
    matrix = 'a) alte Matrix'
  )
`summarise()` ungrouping output (override with `.groups` argument)
new_matrix <- as(ratingMatrix, "data.frame") %>% 
  group_by(item) %>%  
  summarize(
    mean_rating = mean(rating),
    ratings = n()
  ) %>% 
  mutate(
    matrix = 'b) neue Matrix'
  )
`summarise()` ungrouping output (override with `.groups` argument)
comparison <- bind_rows(old_matrix, new_matrix)

ggplot(comparison, aes(x = mean_rating, fill = matrix)) +
  geom_density(alpha = 0.5) +
  scale_y_continuous(expand = c(0,0)) +
  scale_x_continuous(expand = c(0,0)) +
  labs(
    title = "Verteilung mittlere Kundenratings pro Film",
    subtitle = "N = 1664 Filme",
    x = "Durchschnittliche Bewertung", 
    y = "Dichte",
  ) +
  theme_classic() +
  theme(
    text = element_text(size = 12),
    legend.position = c(.90, .95)
  )
Fehler: Argument 3 is empty
Run `rlang::last_error()` to see where the error occurred.
image(ratingMatrix, main = "Raw Ratings")

SPielwiese

ggplot(df2, aes(x=item, y=user, colour=rating)) + geom_point(alpha=1, size = 0.05) + theme_classic()

LS0tCnRpdGxlOiAiQ29sbGFib3JhdGl2ZSBNb3ZpZSBSZWNvbW1lbmRlciIKYXV0aG9yOiAiUGFzY2FsIEJlcmdlciwgTGVhIELDvHRsZXIgJiBKb8OrbCBHcm9zamVhbiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tClItVmVyc2lvbjogKipbRGVmYXVsdF0gWzMyLWJpdF0gQzpcXFByb2dyYW0gRmlsZXNcXFJcXFItNC4xLjAqKgoKKioqIAojIyMjIEluc3RhbGxpZXJlbiBkZXIgUGFja2V0ZQoKYGBge3J9CnBhY2thZ2VzIDwtIGMoInRpZHl2ZXJzZSIsICJkYXRhLnRhYmxlIiwgImx1YnJpZGF0ZSIsICJnZ3Bsb3QyIiwgImdndGhlbWVzIiwgInJlY29tbWVuZGVybGFiIikKCiMgTm9jaCBuaWNodCBpbnN0YWxsaWVydGUgUGFrZXRlIGluc3RhbGxpZXJlbgppbnN0YWxsZWRfcGFja2FnZXMgPC0gcGFja2FnZXMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkKCmlmIChhbnkoaW5zdGFsbGVkX3BhY2thZ2VzID09IEZBTFNFKSkgewogIGluc3RhbGwucGFja2FnZXMocGFja2FnZXNbIWluc3RhbGxlZF9wYWNrYWdlc10pCn0KCiMgTGFkZW4gZGVyIFBhY2tldGUKaW52aXNpYmxlKGxhcHBseShwYWNrYWdlcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkKCiMgc3VtbWFyaWVzIHp1ICJUUlVFIiBzZXR6ZW4gdW0gc3VtbWFyaWVzIGFuenV6ZWlnZW4Kc3VtbWFyaWVzID0gVFJVRQpgYGAKCioqKgojIyMjIERhdGVuaW1wb3J0CgpgYGB7cn0KZGF0YShNb3ZpZUxlbnNlKQpNb3ZpZUxlbnNlCmBgYAoKCmBgYHtyfQojIyBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcmF0aW5ncyBvZiB0aGUgZmlyc3QgdXNlcgpoZWFkKGFzKE1vdmllTGVuc2VbMSxdLCAibGlzdCIpW1sxXV0pCgojIyB2aXN1YWxpemUgcGFydCBvZiB0aGUgbWF0cml4CmltYWdlKE1vdmllTGVuc2VbMToxMDAsMToxMDBdKQoKIyMgbnVtYmVyIG9mIHJhdGluZ3MgcGVyIHVzZXIKaGlzdChyb3dDb3VudHMoTW92aWVMZW5zZSkpCgojIyBudW1iZXIgb2YgcmF0aW5ncyBwZXIgbW92aWUKaGlzdChjb2xDb3VudHMoTW92aWVMZW5zZSkpCgojIyBtZWFuIHJhdGluZyAoYXZlcmFnZWQgb3ZlciB1c2VycykKbWVhbihyb3dNZWFucyhNb3ZpZUxlbnNlKSkKCiMjIGF2YWlsYWJsZSBtb3ZpZSBtZXRhIGluZm9ybWF0aW9uCmhlYWQoTW92aWVMZW5zZU1ldGEpCgojIyBhdmFpbGFibGUgdXNlciBtZXRhIGluZm9ybWF0aW9uCmhlYWQoTW92aWVMZW5zZVVzZXJbImlkIl0pCgpgYGAKIyMgYWxsZSBjaGFyYWt0ZXIgdmFyaWFiZWxuIGZha3RvcmlzaWVyZW4KCmBgYHtyfQoKbW92aWVzIDwtIGFzKE1vdmllTGVuc2UsICJkYXRhLmZyYW1lIikKCm1vdmllcyA8LSBtb3ZpZXMgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikKCmhlYWQobW92aWVzKQoKYGBgCgoKCmBgYHtyfQptb3ZpZXNfd2lkZXIgPC0gcGl2b3Rfd2lkZXIoCiAgbW92aWVzLAogIGlkX2NvbHMgPSB1c2VyLAogIG5hbWVzX2Zyb20gPSBpdGVtLAogIHZhbHVlc19mcm9tID0gcmF0aW5nLAogIHZhbHVlc19maWxsID0gTlVMTCwKKQoKaGVhZChtb3ZpZXNfd2lkZXIpCmBgYAoKKioqCiMjIyMgRXhwbG9yYXRpdmUgRGF0ZW5hbmFseXNlCgpgYGB7cn0KZGZfMSA8LSBtb3ZpZXMgJT4lIGdyb3VwX2J5KGl0ZW0pICU+JSAgc3VtbWFyaXplKG1lYW5fcmF0aW5nID0gbWVhbihyYXRpbmcpKSAlPiUgc2FtcGxlX24oMTUpICU+JSBhcnJhbmdlKGRlc2MobWVhbl9yYXRpbmcpKQoKZ2dwbG90KGRmXzEsIGFlcyh5ID0gcmVvcmRlcihpdGVtLCArbWVhbl9yYXRpbmcpLCB4ID0gbWVhbl9yYXRpbmcpKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAxLCBmaWxsID0gJ3N0ZWVsYmx1ZScpICsKICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMCwwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPXJvdW5kKG1lYW5fcmF0aW5nLDIpKSwgaGp1c3QgPSAxLjMsIGNvbG9yID0gJ3doaXRlJykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJBdmVyYWdlIG1vdmllIHJhdGluZ3MiLAogICAgc3VidGl0bGUgPSAiUmFuZG9tIFNhbXBsZSBvZiAxNSBNb3ZpZXMiLAogICAgeSA9IGVsZW1lbnRfYmxhbmsoKSwgICAgeCA9ICJBdmVyYWdlIFJhdGluZyAoc3RhcnMpIgogICkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMubGluZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSAjIHRleHQgc2l6ZQogICkKCgpgYGAKCioqKgojIyMjIDEuIFdlbGNoZXMgc2luZCBkaWUgYW0gaMOkdWZpZ3N0ZW4gZ2VzY2hhdXRlbiBHZW5yZXMgLyBGaWxtZT8KCmBgYHtyfQptb3ZpZXNfZ2VucmUgPC0gTW92aWVMZW5zZU1ldGElPiUKICByZW5hbWUoaXRlbSA9IHRpdGxlKQptb3ZpZXNfZ2VucmUkdXJsIDwtIE5VTEwKbW92aWVzX2dlbnJlW21vdmllc19nZW5yZSA9PSAwXSA8LSBOQQphIDwtIHdoaWNoKG1vdmllc19nZW5yZT09MSxhcnIuaW5kPVRSVUUpCm1vdmllc19nZW5yZVthXSA8LSBuYW1lcyhtb3ZpZXNfZ2VucmUpW2FbLCJjb2wiXV0KbW92aWVzX2dlbnJlIDwtIG1vdmllc19nZW5yZSAlPiUKICB1bml0ZSgiZ2VucmVzIiwgdW5rbm93bjpXZXN0ZXJuLCBzZXA9ICIsIiwgCiAgICAgICAgcmVtb3ZlID0gVFJVRSwgbmEucm0gPSBUUlVFKQptb3ZpZXNfZ2VucmUKCm1vdmllc19tZWFuIDwtIG1vdmllcyU+JQogIGdyb3VwX2J5KGl0ZW0pJT4lCiAgc3VtbWFyaXNlKG1lYW5fcmF0aW5nID0gbWVhbihyYXRpbmcpKQoKZGYyX21vdmllczwtbWVyZ2UoeD1tb3ZpZXMseT1tb3ZpZXNfZ2VucmUsYnk9Iml0ZW0iLGFsbC54PVRSVUUpJT4lCiAgbXV0YXRlKGdlbnJlcyA9IHN0cnNwbGl0KGFzLmNoYXJhY3RlcihnZW5yZXMpLCAiLCIpKSAlPiUKICB1bm5lc3QoZ2VucmVzKQoKZGYyX21lYW48LW1lcmdlKHg9bW92aWVzX21lYW4seT1tb3ZpZXNfZ2VucmUsYnk9Iml0ZW0iLGFsbC54PVRSVUUpJT4lCiAgbXV0YXRlKGdlbnJlcyA9IHN0cnNwbGl0KGFzLmNoYXJhY3RlcihnZW5yZXMpLCAiLCIpKSAlPiUKICB1bm5lc3QoZ2VucmVzKQpkZjJfbW92aWVzCmBgYApgYGB7cn0KZGYxX21vdmllcyA8LSBkZjIlPiUKICBncm91cF9ieShpdGVtKSU+JQogIHN1bW1hcml6ZShjb3VudD1uKCkpJT4lCiAgdW5ncm91cCgpJT4lCiAgYXJyYW5nZShkZXNjKGNvdW50KSkKCmRmMV9tb3ZpZXMgPC0gaGVhZChkZjFfbW92aWVzLCAxMCkKZGYxX21vdmllcwoKZGYxX21vdmllcyU+JQogIG11dGF0ZShpdGVtID0gZmN0X3Jlb3JkZXIoaXRlbSwgY291bnQpKSU+JQogIGdncGxvdChhZXMoeCA9IGNvdW50LCB5ID0gaXRlbSkpKwogIGdlb21fY29sKGFscGhhID0gMSwgZmlsbCA9ICdzdGVlbGJsdWUnKSsKICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMCwwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPWNvdW50LDIpLCBoanVzdCA9IDEuMywgY29sb3IgPSAnd2hpdGUnKSArCiAgbGFicygKICAgIHRpdGxlID0gIk1vc3QgcmF0ZWQgTW92aWVzIiwKICAgIHkgPSBlbGVtZW50X2JsYW5rKCksICAgIHggPSAiQ291bnQgb2YgcmF0aW5ncyIKICApICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikgIyB0ZXh0IHNpemUKICApCmBgYApEYSBpbiB1bnNlcmVtIGRhdGVuc2F0eiBudXIgZGllIEFuemFobCBSYXJpbmdzIHZvbiBGaWxtZW4gZ2VnZWJlbiBpc3QsIGdlaGVuIHdpciBkYXZvbiBhdXMsIGRhc3MgZGllIG1laXN0IGJld2VydGV0ZW4gRmlsbWUgYXVjaCBkaWUgYW0gaMOkdWZpZ3N0ZW4gZ2VzY2hhdXRlbiBmaWxtZSBzaW5kLiBpbiBkZXIgR3JhZmlrIHNpZWh0IG1hbiBkaWUgMTAgbWVpc3QgYmV3ZXJ0ZXRlbiBGaWxtZSwgd29iZWkgZGllIGVyc3RlbiBkcmVpIFBsw6R0emUgdm9uIFN0YXIgV2FycyBGaWxtZW4gYmVzZXR6dCBzaW5kLgoKYGBge3J9CmRmMV9nZW5yZXMgPC0gZGYyJT4lCiAgZ3JvdXBfYnkoZ2VucmVzKSU+JQogIHN1bW1hcml6ZShjb3VudD1uKCkpJT4lCiAgdW5ncm91cCgpJT4lCiAgYXJyYW5nZShkZXNjKGNvdW50KSkKCmRmMV9nZW5yZXMlPiUKICBtdXRhdGUoZ2VucmVzID0gZmN0X3Jlb3JkZXIoZ2VucmVzLCBjb3VudCkpJT4lCiAgZ2dwbG90KGFlcyh4ID0gY291bnQsIHkgPSBnZW5yZXMpKSsKICBnZW9tX2NvbChhbHBoYSA9IDEsIGZpbGwgPSAnc3RlZWxibHVlJykrCiAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQgPSBjKDAsMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1jb3VudCwyKSwgaGp1c3QgPSAxLjMsIGNvbG9yID0gJ3doaXRlJykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJNb3N0IHJhdGVkIEdlbnJlcyIsCiAgICB5ID0gZWxlbWVudF9ibGFuaygpLCAgICB4ID0gIkNvdW50IG9mIHJhdGluZ3MiCiAgKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy5saW5lLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpICMgdGV4dCBzaXplCiAgKQpgYGAKQXVjaCBoaWVyIHdpcnMgc2F2b24gYXVzZ2VnYW5nZW4sIGRhc3MgZGllIGFtIG1laXN0ZW4gYmV3ZXJ0ZXRlbiBHZW5yZXMgc3VjaCBkaWUgYW0gaMOkdWZpZ3N0IGdlc2NoYXV0ZW4gR2VucmVzIHNpbmQuIEluIGRlciBHcmFmaWsgaXN0IHp1IHNlaGVuLCBkYXNzIERyYW1hIGRhcyB0b3AgR2VucmVzIGlzdCwgZ2Vmb2xndCB2b24gQ29tZWR5IHVuZCBBY3Rpb24uCgojIyMjIDIuIFdpZSB2ZXJ0ZWlsZW4gc2ljaCBkaWUgS3VuZGVucmF0aW5ncyBnZXNhbXRoYWZ0IHVuZCBuYWNoIEdlbnJlcz8KCmBgYHtyfQpnZ3Bsb3QobW92aWVzLCBhZXMoeCA9IHJhdGluZykpICsKICBnZW9tX2JhcihhbHBoYSA9IDEsIGZpbGwgPSAnc3RlZWxibHVlJykgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZlcnRlaWx1bmcgS3VuZGVucmF0aW5ncyBnZXNhbXRoYWZ0IiwKICAgIHN1YnRpdGxlID0gcGFzdGUoIk4gPSAiLCBucm93KGRmMiksICIgQmV3ZXJ0dW5nZW4iKSwKICAgIHggPSAiS3VuZGVuYmV3ZXJ0dW5nZW4iLCAKICAgIHkgPSAiRGljaHRlIiwKICAgIGZpbGwgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScKICApCmBgYApJbiBkaWVzZXIgR3JhZmlrIGlzdCBkaWUgVmVydGVpbHVuZyBkZXIgYmV3ZXJ0dW5nZW4genUgc2VoZW4uIERpZSBCZXdlcnR1bmdlbiA0IHVuZCA1IHdpcmRlbiBrbGFyIGFtIGjDpHVmaWdzdGVuIHZlcmdlYmVuLCB3b2JlaSAxIHVuZCAyIGVoZXIgc2VsdGVuIGJld2VydGV0IHdlcmRlbi4KCmBgYHtyfQpnZ3Bsb3QoZGYyLCBhZXMoeCA9IHJhdGluZywgZmlsbCA9IGdlbnJlcykpICsKICBnZW9tX2JhcihhbHBoYSA9IDEsIGJpbnMgPSAxMCkgKwogIGZhY2V0X3dyYXAofmdlbnJlcykrCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmVydGVpbHVuZyBLdW5kZW5yYXRpbmdzIG5hY2ggR2VucmVzIiwKICAgIHN1YnRpdGxlID0gcGFzdGUoIk4gPSAiLCBucm93KGRmMiksICIgQmV3ZXJ0dW5nZW4iKSwKICAgIHggPSAiRHVyY2hzY2huaXR0bGljaGUgQmV3ZXJ0dW5nIiwgCiAgICB5ID0gIkRpY2h0ZSIsCiAgICBmaWxsID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICBsZWdlbmQucG9zaXRpb24gPSAnbm9uZScKICApCmBgYAoKCkhpZXIgaXN0IHp1IHNlaGVuLCBkYXNzIGRhcyBHZW5yZXMgRHJhbWEgYW0gbWVpc3RlbiBiZXdlcnRldCB3dXJkZSwgd29iZWkgRG9rdW1lbnRhdGlvbmVuIGFtIHdlbmlnc3RlbiBCZXdlcnR1bmdlbiBlcmhhbHRlbiBoYWJlbi4gRGllIEJld2VydHVuZ2VuIHBybyBHZW5yZXMgdmVydGVpbGVuIHNpY2ggamV3ZWlscyBzZWhyIMOkaG5saWNoLiBEaWUgVmVydGVpbHVuZ2VuIGRlciBlaW56ZWxuZW4gR2VucmVzIHNpbmQgZWJlbmZhbGxzIMOkaG5saWNoIHZlcnRlaWx0IHdpZSBkaWUgYmV3ZXJ0dW5nZW4gZ2VzYW10aGFmdC4KCioqKgojIyMjIDMuV2llIHZlcnRlaWxlbiBzaWNoIGRpZSBtaXR0bGVyZW4gS3VuZGVucmF0aW5ncyBwcm8gRmlsbT8KCmBgYHtyfQpkZjMgPC0gbW92aWVzICU+JSAKICBncm91cF9ieShpdGVtKSAlPiUgIAogIHN1bW1hcml6ZSgKICAgIG1lYW5fcmF0aW5nID0gbWVhbihyYXRpbmcpLAogICAgcmF0aW5ncyA9IG4oKQogICkgJT4lIAogIG11dGF0ZSgKICAgIG1vcmVfdGhhbl81MCA9IGlmZWxzZShyYXRpbmdzID49IDUwLCAnYikgbWVociBhbHMgNTAgQmV3ZXJ0dW5nZW4nLCAnYSkgd2VuaWdlciBhbHMgNTAgQmV3ZXJ0dWdlbicpCiAgKQoKZ2dwbG90KGRmMywgYWVzKHggPSBtZWFuX3JhdGluZykpICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAxLCBmaWxsID0gJ3N0ZWVsYmx1ZScpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWZXJ0ZWlsdW5nIG1pdHRsZXJlIEt1bmRlbnJhdGluZ3MgcHJvIEZpbG0iLAogICAgc3VidGl0bGUgPSAiTiA9IDE2NjQgRmlsbWUiLAogICAgeCA9ICJEdXJjaHNjaG5pdHRsaWNoZSBCZXdlcnR1bmciLCAKICAgIHkgPSAiRGljaHRlIiwKICApICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKQogICkKYGBgCkluIGRpZXNlciBHcmFmaWsgaXN0IGRpZSBkdXJjaHNjaG5pdHRsaWNoZSBCZXdlcnR1bmcgcHJvIEZpbG0genUgc2VoZW4sIHdvYmVpIGF1Y2ggaGllciB6dSBzZWhlbiBpc3QgLGRhc3MgZGllIGRpZSBtZWlzdGVuIEZpbG1lIGVpbmUgRHVyY2hzY2huaXR0bGljaGUgQmV3ZXJ0dW5nIHZvbiBjYS4gNCBoYWJlbiAgCgpgYGB7cn0KZ2dwbG90KGRmMywgYWVzKHggPSBtZWFuX3JhdGluZywgZmlsbCA9IG1vcmVfdGhhbl81MCkpICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWZXJ0ZWlsdW5nIG1pdHRsZXJlIEt1bmRlbnJhdGluZ3MgcHJvIEZpbG0iLAogICAgc3VidGl0bGUgPSAiTiA9IDE2NjQgRmlsbWUiLAogICAgeCA9ICJEdXJjaHNjaG5pdHRsaWNoZSBCZXdlcnR1bmciLCAKICAgIHkgPSAiRGljaHRlIiwKICAgIGZpbGwgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScKICApCmBgYApGw7xyIGRpZXNlIEdyYWZpayB3dXJkZW4gZGllIEZpbG1lIGluIHp3ZWkgZ3J1cHBlbiB1bnRlcnRlaWx0OiBGaWxtZSBkaWUgd2VuaWdlciBhbHMgNTAgYmV3ZXJ0dW5nZW4gZXJoYWx0ZW4gaGFiZW4sIHVuZCBGaWxtZSB3ZWxjaGUgbWVociBhbHMgNTAgQmV3ZXJ0dW5nZW4gZXJoYWx0ZW4gaGFiZW4uIEluIGRlciBHcmFmaWsgaXN0IGltZXJub2NoIGRpZSBkdXJjaHNjaG5pdHRsaWNoZSBCZXdlcnR1bmcgZGllc2VyIEZpbG1lIHp1IHNlaGVuIHdvYmVpIGRldXRsaWNoIGVya2FubnQgd2VyZGVuIGthbm4sIGRhc3MgZmlsbWUgd2VsY2hlIHdlbmlnZXIgYmV3ZXJ0dW5nZW4gZXJoYWx0ZW4gaGFiZW4sIHRlbmRlbnppZWxsIGF1Y2ggc2NobGVjaHRlciBiZXdlcnRldCB3dXJkZW4uCgojIyMjIyMgNC5XaWUgc3Rhcmsgc3RyZXVlbiBkaWUgUmF0aW5ncyB2b24gaW5kaXZpZHVlbGxlbiBLdW5kZW4/CmBgYHtyfQoKIyBnZXQgcmF0aW5nIGNvdW50IHBlciB1c2VyLCBhZGQgYXMgY29sdW1uIGZvciBmdXJ0aGVyIHByb2Nlc3NpbmcKY291bnRzIDwtIG1vdmllcyAlPiUgZ3JvdXBfYnkodXNlcikgJT4lIGNvdW50KCkKbW92aWVzIDwtIG1lcmdlKG1vdmllcywgY291bnRzLCBieT0idXNlciIpCm1vdmllc193aWRlciA8LSBtZXJnZShtb3ZpZXNfd2lkZXIsIGNvdW50cywgYnk9InVzZXIiKQoKIyBhdm9pZCB1c2VycyB3aXRoIGFsbW9zdCBubyByYXRpbmdzLCB1c2UgbWVkaWFuIGFzIHRocmVzaG9sZAptZWRpYW5fY291bnQgPC0gbWVkaWFuKGNvdW50cyRuKQpwcmludChtZWRpYW5fY291bnQpCgojIGdldCBzYW1wbGUKc2V0LnNlZWQoNjIzKQptb3ZpZXNfc2FtcGxlIDwtIG1vdmllc193aWRlciAlPiUgZmlsdGVyKG4gPiBtZWRpYW5fY291bnQpICU+JSBzYW1wbGVfbig1KQoKIyBjcmVhdGUgbG9uZyB0YWJsZQptb3ZpZXNfc2FtcGxlX2xvbmcgPC0gZmlsdGVyKG1vdmllcywgdXNlciAlaW4lIG1vdmllc19zYW1wbGUkdXNlcikKCiMgZHJvcCBpdGVtIG5hbWVzLCAKbW92aWVzX3NhbXBsZV9sb25nIDwtIHN1YnNldChtb3ZpZXNfc2FtcGxlX2xvbmcsIHNlbGVjdCA9IC1jKGl0ZW0pKQoKbW92aWVzX3NhbXBsZQptb3ZpZXNfc2FtcGxlX2xvbmcKCgpgYGAKCmBgYHtyfQojIE51bWJlciBvZiByYXRpbmdzIHBlciB1c2VyIHBlciByYXRpbmcgdmFsdWUKbW92aWVzX3NhbXBsZV9sb25nX2dyb3VwZWQgPC0gbW92aWVzX3NhbXBsZV9sb25nICU+JSBncm91cF9ieSh1c2VyLCByYXRpbmcpICU+JSBzdW1tYXJpc2UocmF0aW5nX2RlbnMgPSBsZW5ndGgodXNlcikgLyBmaXJzdChuKSwgdXNlciA9IGZpcnN0KHVzZXIpLCBuPWZpcnN0KG4pLCByYXRpbmcgPSBmaXJzdChyYXRpbmcpKQptb3ZpZXNfc2FtcGxlX2xvbmdfZ3JvdXBlZAptb3ZpZXNfc2FtcGxlX2xvbmcKCmBgYAoKCgpgYGB7cn0KCmdncGxvdChtb3ZpZXNfc2FtcGxlX2xvbmdfZ3JvdXBlZCwgYWVzKHg9cmF0aW5nLCB5PXJhdGluZ19kZW5zLCBmaWxsPXVzZXIpKSArIAogIGdlb21fY29sKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCkpICsgCiAgbGFicygKICAgIHRpdGxlID0gIlN0cmV1dW5nIEt1bmRlbnJhdGluZ3MgZsO8ciB6dWbDpGxsaWcgZ2V3w6RobHRlIEt1bmRlbiIsCiAgICBzdWJ0aXRsZSA9ICJOID0gNSBLdW5kZW4iLAogICAgeCA9ICJVc2VyIHJhdGluZyAoMS01KSIsIAogICAgeSA9ICJBdXNwcsOkZ3VuZyBSYXRpbmciLAogICkgKwogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScKICApCnJsYW5nOjpsYXN0X2Vycm9yKCkKcmxhbmc6Omxhc3RfdHJhY2UoKQpgYGAKCkluIGRpZXNlciBHcmFmaWsgc2VoZW4gd2lyLCB3aWUgc2ljaCBkaWUgQmV3ZXJ0dW5nZW4gZWluemVsbmVyIEt1bmRlbiB2ZXJ0ZWlsZW4uIEF1ZmZhbGxlbmQgaXN0IGdlbmVyZWxsLCBkYXNzIGRpZSBCZXdlcnR1bmdlbiAxIHVuZCAyIHdlbmlnZXIgb2Z0IGFiZ2VnZWJlbiB3dXJkZSBhbHMgMyB1bmQgNC4gCkJlaSBkZXIgVmVydGVpbHVuZyBkZXIgcmF0aW5ncyBzaW5kIHZvbiBVc2VyIHp1IFVzZXIgVW50ZXJzY2hpZWRlIGZlc3RzdGVsbGJhci4gVXNlciAyNCBiZXdlcnRldCBiZWlzcGllbHN3ZWlzZSB2aWVsIGJlc3NlciBhbHMgVXNlciA2MzkuIERpZXMga8O2bm50ZSBiZWRldXRlbiwgZGFzcyBVc2VyIDI0IG51ciBGaWxtZSBiZXdlcnRldCBvZGVyIHNjaGF1dCBkaWUgZXIvc2llIG1hZywgb2RlciBncnVuZHPDpHR6bGljaCBow7ZoZXJlIEJld2VydHVuZ2VuIGFiZ2lidC4gTGVpZGVyIHNlaGVuIHdpciBoaWVyIHdlbmlnZXIgZ3V0LCB3ZWxjaGUgVGVuZGVuemVuIGRpZSBTdHJldXVuZyBkZXIgUmF0aW5nIGFsbGVyIFVzZXIgYXVmd2Vpc2VuLgoKCmBgYHtyfQoKbW92aWVzX3NwYW4gPC0gbW92aWVzICU+JSBncm91cF9ieSh1c2VyKSAlPiUgCiAgc3VtbWFyaXplKG1lYW4gPSBtZWFuKHJhdGluZyksIG1pbiA9IG1pbihyYXRpbmcpLCBtYXggPSBtYXgocmF0aW5nKSwgc3BhbiA9IChtYXgocmF0aW5nKSAtIG1pbihyYXRpbmcpKSkKCm1vdmllc19zcGFuCiAgCmBgYAoKYGBge3J9CnNldC5zZWVkKDEyMykKCmdncGxvdChzYW1wbGVfbihtb3ZpZXNfc3BhbiwgMjApLCBhZXMoeD11c2VyKSkgKwogIGdlb21fcG9pbnQoY29sb3VyPSJibGFjayIsIGFlcyh5PW1lYW4pLCBzaGFwZT0yMSkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWluLCB5bWF4PW1heCkpICsKICBsYWJzKAogICAgdGl0bGUgPSAiU3Bhbm53ZWl0ZSBLdW5kZW5yYXRpbmdzICIsCiAgICBzdWJ0aXRsZSA9ICJOID0gMjAgS3VuZGVuIiwKICAgIHggPSAiVXNlciBJRCIsIAogICAgeSA9ICJSYXRpbmcgUmFuZ2UiLAogICkKCgpnZ3Bsb3QobW92aWVzX3NwYW4sIGFlcyh4PXVzZXIpKSArCiAgZ2VvbV9iYXIoY29sb3VyPSJibGFjayIsIGFlcyhzcGFuKSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJTcGFubndlaXRlIEt1bmRlbnJhdGluZ3MiLAogICAgc3VidGl0bGUgPSAiIiwKICAgIHggPSAiU3Bhbm53ZWl0ZSIsIAogICAgeSA9ICJBbnphaGwgVXNlciIsCiAgKQogIApgYGAKCkluIGRpZXNlbiBHcmFmaWtlbiBzZWhlbiB3aXIgZGV0YWlsbGllcnRlcmUgSW5mb3JtYXRpb25lbiDDvGJlciBkaWUgU3Bhbm53ZWl0ZSB1bmQgZGVuIE1pdHRlbHB1bmt0LiBJbiBkZXIgZXJzdGVuIMOcYmVyc2ljaHQgaXN0IGRpZSBTcGFubndlaXRlIHVuZCBkZXIgTWl0dGVscHVua3QgZWluemVsbmVyIEt1bmRlbiBkYXJnZXN0ZWxsdC4gRXMgZsOkbGx0IGF1ZiwgZGFzcyB0cm90eiBkZXMgdGVpbHdlaXNlIHJlbGF0aXYgaG9oZW0gTWl0dGVsd2VydCBhbGxlIFJhdGluZ3Mgdm9uIDEtNSBhYmdlZ2ViZW4gd3VyZGVuLiBFaW4gcmF0aW5nIHZvbiA1IHd1cmRlIHNvenVzYWdlbiBpbW1lciBhYmdlZ2ViZW4sIDEgbmljaHQgaW1tZXIuCkluIGRlciB6d2VpdGVuIMOcYmVyc2ljaHQgaXN0IGRpZSBTcGFubndlaXRlIGFsbGVyIEt1bmRlbiBkYXJnZXN0ZWxsdC4gSGllciB3aXJkIHNpY2h0YmFyLCBkYXNzIGRpZSBtZWlzdGVuIEt1bmRlbiBCZXdlcnR1bmdlbiB2b24gMS01IGFiZ2VnZWJlbiBoYWJlbiAoU3Bhbm53ZWl0ZT00KSwgdW5kIG51ciB3ZWluaWdlIHNlaHIgaG9tb2dlbiBiZXdlcnRldCBoYWJlbiAoU3Bhbm53ZWl0ZSA9IDEvMikuIEVpbmUga2xlaW5lIFNwYW5ud2VpdGUga2FubiBoaWVyIGF1Y2ggYXVmZ2V0cmV0ZW4gc2VpbiwgZGEgZGllc2UgVXNlciBzZWhyIHdlbmlnZSBCZXdlcnR1bmdlbiBhYmdlZ2ViZW4gaGFiZW4uCgoKIyMjIyMjIDUuV2VsY2hlbiBFaW5mbHVzcyBoYXQgZGllIE5vcm1pZXJ1bmcgZGVyIFJhdGluZ3MgcHJvIEt1bmRlIGF1ZiBkZXJlbiBWZXJ0ZWlsdW5nPwpgYGB7cn0KaGlzdChnZXRSYXRpbmdzKE1vdmllTGVuc2UpLCBicmVha3M9MTUpCmhpc3QoZ2V0UmF0aW5ncyhNb3ZpZUxlbnNlTm9ybSksIGJyZWFrcz00MCkKYGBgCkRpZSBSYXRpbmdzIHNpbmQgbnVuIHVuZ2Vmw6RociBOb3JtYWx2ZXJ0ZWlsdCBtaXQgZWluZW0gRHVyY2hzY2huaXR0c3JhdGluZyB2b24gMCB1bmQgZWluZXIgU3RhbmRhcmRhYndlaWNodW5nIHZvbiAxLiAKRXJrZW5uYmFyIGlzdCwgZGFzcyBkaWUgVmVydGVpbHVuZyByZWNodHNzdGVpbCB1bmQgbGlua3NzY2hpZWYgaXN0LCBhbHNvIG1laHJoZWl0bGljaCBwb3NpdGl2ZSBCZXdlcnR1bmdlbiBhYmdlZ2ViZW4gd3VyZGVuLiAKRHVyY2ggZGllIE5vcm1pZXJ1bmcgZGVyIERhdGVuIHdlcmRlbiBkaWUgUmF0aW5ncyBqZWRlcyBVc2VycyBhdWYgZGllc2VsYmUgVmVydGVpbHVuZyBnZXN0YXVjaHQsIHdvZHVyY2ggbWFuIGRpZSBWZXJ0ZWlsdW5nIGFsbGVyIERhdGVuIGFuYWx5c2llcmVuIGthbm4uIERhZHVyY2ggaGF0IG1hbiBiZWlzcGllbHN3ZWlzZSBkaWUgTcO2Z2xpY2hrZWl0IGRpZSBkdXJjaHNjaG5pdHRsaWNoZSBCZXdlcnR1bmdzdGVuZGVueiBoZXJhdXN6dWZpbmRlbi4gCgoKCiMjIyMjIyA2LldlbGNoZSBzdHJ1a3R1cmVsbGVuIENoYXJha3RlcmlzdGlrYSAoei5CLiBTcGFyc2l0eSkgdW5kIEF1ZmbDpGxsaWdrZWl0ZW4gemVpZ3QgZGllIFVzZXIgSXRlbSBNYXRyaXg/CmBgYHtyfQppbWFnZShNb3ZpZUxlbnNlLCBtYWluID0gIlJhdyBSYXRpbmdzIikKCk1vdmllTGVuc2VOb3JtIDwtIG5vcm1hbGl6ZShNb3ZpZUxlbnNlLCBtZXRob2Q9Ilotc2NvcmUiKQppbWFnZShNb3ZpZUxlbnNlTm9ybSwgbWFpbiA9ICJOb3JtYWxpemVkIFJhdGluZ3MiKQpgYGAKKioqCgpVc2VycyBtaXQgdGllZmVuIElEJ3MgdW5kIEZpbG1lIG1pdCBob2hlbiBJRCdzIHdlaXNlbiB3ZW5pZ2VyIHJhdGluZ3MgYXVmLiBGaWxtZSBtaXQgdGllZmVyIElEIGplZG9jaCBzZWhyIHZpZWxlLgpBdWZmYWxsZW5kIGlzdCwgZGFzcyBlcyBlaW5pZ2Ugd2VuaWdlIFVzZXIgZ2lidCwgZGllIGZhc3QgYWxsZSBGaWxtZSBiZXdlcnRldCBoYWJlbiAoZXJrZW5uYmFyIGR1cmNoIGRpZSBob3Jpem9udGFsZW4gc2NoYXJ6ZW4gU3RyaWNoZSkuIERpZXMgc2NoZWluZW4gc2VociBha3RpdmUgQmV3ZXJ0ZXIgenUgc2Vpbi4KVmllbGUgVXNlcnMgaGFiZW4gamVkb2NoIG51ciBlaW5lbiBrbGVpbmVuIFRlaWwgZGVyIEZpbG1lIGJld2VydGV0LgpCZWkgZGVuIEZpbG1lbiBpc3QgZWluZSDDpGhubGljaGUgVGVuZGVueiB3YWhyenVuZWhtZW4sIGplZG9jaCBzaW5kIGRpZSB2ZXJ0aWthbGVuIFN0cmljaGUgYnJlaXRlci4gTcO2Z2xpY2hlcndlaXNlIHNpbmQgZG9ydCBlaW5pZ2UgYmVsaWVidGUgRmlsbWUgenVzYW1tZW5nZWZhc3N0LgoKCgoKKioqCiMjIyMjIERhdGVucmVkdWt0aW9uCgpgYGB7cn0KIyBjb252ZXJ0IGludG8gZGYKZGF0YSA8LSBhcyhNb3ZpZUxlbnNlLCAiZGF0YS5mcmFtZSIpCgojIGdldCB0aGUgNDAwIHVzZXJzIHdpdGggbW9zdCByYXRpbmdzCmNvdW50cyA8LSBkYXRhICU+JSBncm91cF9ieSh1c2VyKSAlPiUgY291bnQoKSAlPiUgYXJyYW5nZShkZXNjKG4pLCB1c2VyKSAlPiUgaGVhZCg0MDApCmRhdGEgPC0gaW5uZXJfam9pbihjb3VudHMsIGRhdGEsIGJ5PSJ1c2VyIikKZGF0YSA8LSBkYXRhICU+JSBzZWxlY3QodXNlciwgaXRlbSwgcmF0aW5nKSAlPiUgdW5ncm91cApkYXRhIDwtIGFzLmRhdGEuZnJhbWUoZGF0YSkKCiMgZ2V0IHRoZSA3MDAgTW92aWVzIHdpdGggbW9zdCByYXRpbmdzCmNvdW50cyA8LSBkYXRhICU+JSBncm91cF9ieShpdGVtKSAlPiUgY291bnQoKSAlPiUgYXJyYW5nZShkZXNjKG4pLCBpdGVtKSAlPiUgaGVhZCg3MDApCmRhdGEgPC0gaW5uZXJfam9pbihjb3VudHMsIGRhdGEsIGJ5PSJpdGVtIikKZGF0YSA8LSBkYXRhICU+JSBzZWxlY3QodXNlciwgaXRlbSwgcmF0aW5nKSAlPiUgdW5ncm91cApkYXRhIDwtIGFzLmRhdGEuZnJhbWUoZGF0YSkKCiMgY29udmVydCBiYWNrIGludG8gcmVhbFJhdGluZ01hdHJpeApyYXRpbmdNYXRyaXggPC0gYXMoZGF0YSwgInJlYWxSYXRpbmdNYXRyaXgiKQpgYGAKCmBgYHtyfQpyYXRpbmdNYXRyaXgKYGBgCmBgYHtyfQpwcmludChwYXN0ZSgnT2xkIE1hdHJpeCAoJyx0b1N0cmluZyhkaW0oTW92aWVMZW5zZSkpLCAnKScpKQpwcmludChwYXN0ZSgnTm9uIE5BIFZhbHVlczonLCByb3VuZCgobnJhdGluZ3MoTW92aWVMZW5zZSkgLyAoZGltKE1vdmllTGVuc2UpWzFdICogZGltKE1vdmllTGVuc2UpWzJdKSkgKiAxMDAsMiksICclJyApKQpwcmludCgnJykKcHJpbnQocGFzdGUoJ05ldyBNYXRyaXggKCcsdG9TdHJpbmcoZGltKHJhdGluZ01hdHJpeCkpLCAnKScpKQpwcmludChwYXN0ZSgnTm9uIE5BIFZhbHVlczonLCByb3VuZCgobnJhdGluZ3MocmF0aW5nTWF0cml4KSAvIChkaW0ocmF0aW5nTWF0cml4KVsxXSAqIGRpbShyYXRpbmdNYXRyaXgpWzJdKSkgKiAxMDAsMiksICclJykpCgoKYGBgCgpgYGB7cn0Kb2xkX21hdHJpeCA8LSBhcyhNb3ZpZUxlbnNlLCAiZGF0YS5mcmFtZSIpICU+JSAKICBncm91cF9ieShpdGVtKSAlPiUgIAogIHN1bW1hcml6ZSgKICAgIG1lYW5fcmF0aW5nID0gbWVhbihyYXRpbmcpLAogICAgcmF0aW5ncyA9IG4oKQogICkgJT4lIAogIG11dGF0ZSgKICAgIG1hdHJpeCA9ICdhKSBhbHRlIE1hdHJpeCcKICApCgpuZXdfbWF0cml4IDwtIGFzKHJhdGluZ01hdHJpeCwgImRhdGEuZnJhbWUiKSAlPiUgCiAgZ3JvdXBfYnkoaXRlbSkgJT4lICAKICBzdW1tYXJpemUoCiAgICBtZWFuX3JhdGluZyA9IG1lYW4ocmF0aW5nKSwKICAgIHJhdGluZ3MgPSBuKCkKICApICU+JSAKICBtdXRhdGUoCiAgICBtYXRyaXggPSAnYikgbmV1ZSBNYXRyaXgnCiAgKQoKY29tcGFyaXNvbiA8LSBiaW5kX3Jvd3Mob2xkX21hdHJpeCwgbmV3X21hdHJpeCkKCmdncGxvdChjb21wYXJpc29uLCBhZXMoeCA9IG1lYW5fcmF0aW5nLCBmaWxsID0gbWF0cml4KSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZlcnRlaWx1bmcgbWl0dGxlcmUgS3VuZGVucmF0aW5ncyBwcm8gRmlsbSIsCiAgICBzdWJ0aXRsZSA9ICJOID0gMTY2NCBGaWxtZSIsCiAgICB4ID0gIkR1cmNoc2Nobml0dGxpY2hlIEJld2VydHVuZyIsIAogICAgeSA9ICJEaWNodGUiLAogICkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICBsZWdlbmQucG9zaXRpb24gPSBjKC45MCwgLjk1KQogICkKYGBgCgpgYGB7cn0KaW1hZ2UocmF0aW5nTWF0cml4LCBtYWluID0gIlJhdyBSYXRpbmdzIikKYGBgCgoKCgoKCgoKCgoKCgoKCiMjIyMjIyBTUGllbHdpZXNlCmBgYHtyfQoKZ2dwbG90KGRmMiwgYWVzKHg9aXRlbSwgeT11c2VyLCBjb2xvdXI9cmF0aW5nKSkgKyBnZW9tX3BvaW50KGFscGhhPTEsIHNpemUgPSAwLjA1KSArIHRoZW1lX2NsYXNzaWMoKQoKYGBgCg==